﻿/*
 * Author: .net follower (http://dotnetfollower.com)
 * Copyright: � 2012 .net follower 
 * Usage: software is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND.
 */

using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using fastJSON;

namespace WebLoading
{
    /// <summary>    
    /// Abstracts the json data loading from a specified source using Http. Provides such basic steps as data loading, verification and interpretation.
    /// </summary>
    /// <typeparam name="TParsedData">A type of the analyzed data to be returned</typeparam>
    public abstract class JsonLoader<TParsedData> : HttpLoader<Dictionary<string, object>, TParsedData> where TParsedData : class
    {
        /// <summary>
        /// Initializes a new instance of the JsonLoader class
        /// </summary>
        /// <param name="url">An initial url to load data from</param>
        public JsonLoader(string url) : base(url)
        {
        }

        /// <summary>
        /// Initializes a new instance of the JsonLoader class
        /// </summary>
        public JsonLoader()
        {
        }

        /// <summary>        
        /// Reads json data from a stream and represents it as a tree of key-value collections
        /// </summary>
        /// <param name="stream">A data stream</param>
        /// <returns>A root key-value collection</returns>
        protected override Dictionary<string, object> ReadResponseStream(Stream stream)
        {
            StreamReader reader = new StreamReader(stream);
            string jsonText = reader.ReadToEnd();

            JsonParser parser = new JsonParser(jsonText, false);
            return parser.Decode() as Dictionary<string, object>;
        }

        /// <summary>
        /// Obtains object from a tree of key-value collections using a xpath-like path (for example, "set/items/item")
        /// </summary>
        /// <param name="dictionary">A root key-value collection</param>
        /// <param name="path">A xpath-like path</param>
        /// <returns>A found object</returns>
        protected static object SelectObject(Dictionary<string, object> dictionary, string path)
        {
            List<object> res = SelectObjects(dictionary, path, true, true);
            return res.Count > 0 ? res[0] : null;
        }

        /// <summary>
        /// Obtains object from a tree of key-value collections using a xpath-like path (for example, "set/items/item")
        /// </summary>
        /// <param name="dictionary">A root key-value collection</param>
        /// <param name="path">A xpath-like path</param>
        /// <param name="returnNull">Determines if nulls should be returned or skipped</param>
        /// <returns>A found object</returns>
        protected static List<object> SelectObjects(Dictionary<string, object> dictionary, string path, bool returnNull)
        {
            return SelectObjects(dictionary, path, returnNull, false);
        }

        /// <summary>
        /// Obtains object from a tree of key-value collections using a xpath-like path (for example, "set/items/item")
        /// </summary>
        /// <param name="dictionary">A root key-value collection</param>
        /// <param name="path">A xpath-like path</param>
        /// <param name="returnNull">Determines if nulls should be returned or skipped</param>
        /// <param name="onlyFirstFound">Determines if the search should be stopped after the first found object</param>
        /// <returns>A found object</returns>
        private static List<object> SelectObjects(Dictionary<string, object> dictionary, string path, bool returnNull, bool onlyFirstFound)
        {
            List<object> res = new List<object>();

            string[] pathPatterns = path.Split(new string[] { "/" }, StringSplitOptions.RemoveEmptyEntries);

            List<Dictionary<string, object>> dicts = new List<Dictionary<string, object>>() { dictionary };
            for (int i = 0; i < pathPatterns.Length; i++)
            {
                string pattern = pathPatterns[i];

                Dictionary<string, object>[] dictsToProcess = dicts.ToArray();
                dicts.Clear();

                foreach (Dictionary<string, object> currentDictionary in dictsToProcess)
                {
                    if (currentDictionary.ContainsKey(pattern))
                    {
                        object obj = currentDictionary[pattern];

                        if (i == pathPatterns.Length - 1) // is it last pattern
                        {
                            if (obj != null || returnNull)
                                res.Add(obj);
                        }
                        else // move forward
                        {
                            if (obj is Dictionary<string, object>)
                                dicts.Add(obj as Dictionary<string, object>);
                            else
                                if (obj is IEnumerable)
                                    foreach (object childObj in obj as IEnumerable)
                                        if (childObj is Dictionary<string, object>)
                                            dicts.Add(childObj as Dictionary<string, object>);
                        }
                    }

                    if (onlyFirstFound && res.Count > 0)
                        break;
                }

                if (dicts.Count == 0)
                    break;
            }

            return res;
        }
    }
}
